package org.vectrics.hibernate;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.vectrics.DataException;
import org.vectrics.SystemException;
import org.vectrics.common.db.AuditInterceptor;
import org.vectrics.common.db.DummyEventGenerator;
import org.vectrics.common.db.EventGenerator;
import org.vectrics.config.ApplicationConfiguration;
import org.vectrics.provider.CoreServiceLocator;



abstract public class HibernateUtil {
	private transient static Logger log = Logger.getLogger(HibernateUtil.class);
	private SessionFactory sessionFactory = null;

    private String providerClassName = null;
	//private Interceptor interceptor = new AuditInterceptor();
	private int openConnectionCount = 0;
	private Configuration config = null;
	private String databaseUser = "flashrecruit";
	private String databasePassword = "vectrics";
	
	public Configuration getHibernateConfiguration() {
		return(this.config);
	}
	
	protected abstract void registerMappings(Configuration cfg) throws MappingException;
	
	public abstract String getDataSource();
	

	public HibernateUtil() {
		log.debug("HibernateUtil.Constructor called");
		
	}
	public SessionFactory getSessionFactory() {
		return(sessionFactory);
	}

	/**
	 * @param sessionFactory the sessionFactory to set
	 */
	public void setSessionFactory(final SessionFactory sessionFactory) {
	    this.sessionFactory = sessionFactory;
	}
	
	public String getDatabasePassword() {
		return databasePassword;
	}

	public void setDatabasePassword(String databasePassword) {
		this.databasePassword = databasePassword;
	}

	public String getDatabaseUser() {
		return databaseUser;
	}

	public void setDatabaseUser(String databaseUser) {
		this.databaseUser = databaseUser;
	}

	public static void deleteObjects(Iterator iter, Session session) {
		while (iter.hasNext()) {
			Object object = iter.next();
			session.delete(object);
		}
	}
	
	public void closeSessionOld(Session session) {
		
	}
	
	public void initialize(boolean inContainer) {
	    
		try {
			//ClassLoader classLoader = getClass().getClassLoader();
			config = new Configuration();
			if (inContainer) {
				setProperties(config);
			}
			else {
				setTestProperties(config);
			}
			
			registerMappings(config);
			
			log.debug("Creating hibernate session factory..");
			sessionFactory = config.buildSessionFactory();
			sessionFactory.evictQueries();
			log.debug("Creating hibernate session factory - done");
		}
		catch (Exception e) {
		    throw new SystemException("Exception initializing Hibernate", e);
		}
	}
	
	abstract protected String getTestConnectionUrl();

	
	public void save(Object object) {
		Session session = null;
		session = ThreadProperties.getSession();
		session.save(object);
	}

	public void update(Object object) {
		Session session = null;
		session = ThreadProperties.getSession();
		session.update(object);
	}

	public void saveOrUpdate(Object object) {
		Session session = null;
		try {
			session = ThreadProperties.getSession();
			session.saveOrUpdate(object);
		} finally {
		}
	}
	
	protected void setOutOfContainerProperties(Configuration cfg) {
	    ApplicationConfiguration config = CoreServiceLocator.getConfiguration();
		cfg.setProperty("hibernate.connection.url", getTestConnectionUrl());
		cfg.setProperty("hibernate.connection.username", getDatabaseUser());
		cfg.setProperty("hibernate.connection.password", getDatabasePassword());
		cfg.setProperty("hibernate.connection.pool_size", "60");
		cfg.setProperty("hibernate.c3p0.max_size", "60");
		cfg.setProperty("hibernate.dialect", config.getHibernateDialect());
		cfg.setProperty("hibernate.connection.driver_class", config.getJdbcDriver());
		cfg.setProperty("hibernate.jdbc.batch_size", "0");
		cfg.setProperty("hibernate.jdbc.use_scrollable_resultset", "true");
		cfg.setProperty("hibernate.connection.release_mode", "after_statement");
		cfg.setProperty("hibernate.cache.use_second_level_cache", "false");
	}
	
	protected void setTestProperties(Configuration cfg) {
	    this.setOutOfContainerProperties(cfg);
	}

	

	protected void setProperties(Configuration cfg) {
	    ApplicationConfiguration config = CoreServiceLocator.getConfiguration();
	    // Se carga el dataSource
	    cfg.setProperty("hibernate.connection.datasource", getDataSource());
	    // Cargo las propiedades.
	    Set<String> properties = config.getHibernateProperties().keySet();
	    // Se recorre las propiedades de hibernate y se setean.
	    for (String property : properties) {
	    	cfg.setProperty(property, config.getHibernateProperties().get(property));
	    }
	    cfg.setProperty("hibernate.dialect", config.getHibernateDialect());

	    if (this.getProviderClassName() != null) {
	    	cfg.setProperty("hibernate.connection.provider_class", this.getProviderClassName());
	    }
	}


	/**
	 * @return Returns the providerClassName.
	 */
	public String getProviderClassName() {
		return providerClassName;
	}
	/**
	 * @param providerClassName The providerClassName to set.
	 */
	public void setProviderClassName(String providerClassName) {
		this.providerClassName = providerClassName;
	}
	
	
	public void closeSession() {
		ThreadProperties.closeSession();
	}
	
	
	public void closeSessionNoFlushOld(Session session) {
		
	}
	
	
	
	public void createSchema() {
		String[] args = new String[0];
		org.hibernate.tool.hbm2ddl.SchemaExport.main(args);		
	}

	public Session openSession(Connection connection) {
		ThreadLocal threadLocal = new ThreadLocal();
		Session session = (Session)threadLocal.get();
		return(session);
	}

	
	public Session createNewSession() throws HibernateException {
		if (log.isDebugEnabled())
			log.debug("createSession() called - thread: " + Thread.currentThread().getName());
		
	    if (sessionFactory == null)
	        throw new SystemException("Hibernate util cannot be used before initialize() called");
	    
		Session session = null;
		try {
			log.debug("openSession() - opening session on instance");
			if (ThreadProperties.getEventsOff()) {
				DummyEventGenerator dummyEventGenerator = new DummyEventGenerator();
				session = this._createSession(dummyEventGenerator);
			} else {
	        	EventGenerator eventGenerator = (EventGenerator)CoreServiceLocator.findServiceBean("event.generator");
				session = this._createSession(eventGenerator);
			}
		}
		catch (Exception ex) {
			throw new HibernateException("Exception creating new session", ex);
		}
		log.debug("openSession() - done finding session");
		log.debug("HibernateUtil() - ############ OPENED SESSION ################");
		
		return(session);
	}
	

	public Session getThreadSession() {
		Session session = ThreadProperties.getSession();
		if (session == null)
			throw new SystemException("Session never set on ThreadLocal");
		return(session);
	}
	

	private boolean getUseHibernateTransactions() {
		return(true);   
	}

	
	private Session _createSession(EventGenerator eventGenerator) throws HibernateException {
		Session session = null;
		if (eventGenerator == null) {
			throw new SystemException("Passed a null event generator");
		} else {
			session = sessionFactory.openSession(new AuditInterceptor(eventGenerator));
		}
		
		if (session == null) {
			log.error("Error opening session - session is null after open attempt.");
			throw new HibernateException("Error opening session - session is null after open attempt.");
		}
		this.openConnectionCount++;
		if (log.isDebugEnabled())
			log.debug("(+) Hibernate connection opened, connection count = " + openConnectionCount);
		return (session);
	}
	
	
	private Session _openSession(Connection connection) throws HibernateException {
		Session session = sessionFactory.openSession(connection);
		if (session == null) {
			log.error("Error opening session - session is null after open attempt.");
			throw new HibernateException("Error opening session - session is null after open attempt.");
		}
		this.openConnectionCount++;
		if (log.isDebugEnabled())
			log.debug("(+) Hibernate connection opened, connection count = " + openConnectionCount);
		return (session);
	}

	public static List findElementsFromFilter(Filter filter, int start, int end) {
	    List elements = new ArrayList();
		Session session = null;
		try {
			session = ThreadProperties.getSession();
			
			Query query = null;
			query = filter.createQuery(session);
	
			if (log.isDebugEnabled()) {
				log.debug("Statement = " + filter.getStatement());
			}
			if ((end >= 0) && (start >= 0)) {
				query.setMaxResults(1 + end - start);
				query.setFirstResult(start);
			}
			elements.addAll(query.list());
		}
		catch (HibernateException he) {
			log.error("Hibernate exception running filter", he);
			log.error(" - qry = " + filter.getStatement());
			throw new SystemException("Hibernate exception running filter", he);
		}
		finally {
		}
		return(elements);
    }	
	
	
	public static List findElements(Filter filter, int startIndex, int endIndex) {
		log.debug("findElements(criteria, startIndex, endIndex)");
		Session session = null;
		List list = null;
		try {
			if (log.isDebugEnabled()) {
				log.debug(" - statment = " + filter.getStatement());
			}

			session = ThreadProperties.getSession();
			
			Query query = null;
			query = filter.createQuery(session);

			
			if ((endIndex >= 0) && (startIndex >= 0)) {
				query.setMaxResults(1 + endIndex - startIndex);
				query.setFirstResult(startIndex);
			}
			list = query.list();
		}
		catch (HibernateException ex) {
			log.error("findElements() - exception", ex);
			throw new DataException(ex);
		}
		finally {
			//if (session != null) {
			//	this.closeSessionOld(session);
			//}
		}
		log.debug("done with findElements(criteria, startIndex, endIndex)");
		return(list);
	}
	
	
	public static int findElementCount(Filter filter, String distinctElement) {
		log.debug("findPersonCount(criteria)");

		filter.addSelectField("count(distinct " + distinctElement + ")");
		if (log.isDebugEnabled()) {
			log.debug(" - statement = " + filter.getStatement());
		}
		int count = 0;
		Session session = null;
		try {
			session = ThreadProperties.getSession();
			
			Query query = null;
			query = filter.createQuery(session);
			
			Object obj = query.iterate().next();
			Long countInt = (Long)obj;
			count = countInt.intValue();
		}
		catch (HibernateException ex) {
			log.error("findElementCount() - exception", ex);
			throw new DataException(ex);
		}
//		finally {
//			if (session != null) {
//				this.closeSessionOld(session);
//			}
//		}
		if (log.isDebugEnabled())
		    log.debug("done with findElementCount(criteria, startIndex, endIndex), count = " + count);
		
		return(count);
	}
	
	
}